package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.search.HomePageVo;
import org.apache.commons.lang3.StringUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {
    @Resource
    private ElasticsearchTemplate elasticsearchTemplate;
    @Resource
    private AlbumInfoFeignClient albumInfoFeignClient;
    @Resource
    private UserInfoFeignClient userInfoFeignClient;
    @Resource
    private CategoryFeignClient categoryFeignClient;
    @Resource
    private ElasticsearchClient elasticsearchClient;

    @Override
    public void upper(Long albumId) {
        Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
        Assert.notNull(albumInfoResult,"数据导入时，获取专辑信息失败");
        AlbumInfo albumInfo = albumInfoResult.getData();

        //1
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        BeanUtils.copyProperties(albumInfo,albumInfoIndex);
        //2
        Result<UserInfo> userInfoResult = userInfoFeignClient.getUserInfo(albumInfo.getUserId());
        Assert.notNull(userInfoResult,"数据导入时，获取主播昵称失败");
        UserInfo userInfoResultData = userInfoResult.getData();
        if (userInfoResultData!=null){
            albumInfoIndex.setAnnouncerName(userInfoResultData.getNickname());
        }
        //3
        Result<BaseCategoryView> categoryView = categoryFeignClient.getCategoryView(albumInfo.getCategory3Id());
        Assert.notNull(categoryView,"数据导入时，获取三级分类id失败");
        BaseCategoryView categoryViewData = categoryView.getData();
        if (categoryViewData!=null){
            albumInfoIndex.setCategory1Id(categoryViewData.getCategory1Id());
            albumInfoIndex.setCategory2Id(categoryViewData.getCategory2Id());
            albumInfoIndex.setCategory3Id(categoryViewData.getCategory3Id());
        }
        //4
        int playNum=(new Random().nextInt()+1)*100000;
        int subscribeNum=(new Random().nextInt()+1)*100000;
        int buyNum=(new Random().nextInt()+1)*100000;
        int commentNum=(new Random().nextInt()+1)*100000;
        albumInfoIndex.setPlayStatNum(playNum);
        albumInfoIndex.setSubscribeStatNum(subscribeNum);
        albumInfoIndex.setBuyStatNum(buyNum);
        albumInfoIndex.setCommentStatNum(commentNum);
        albumInfoIndex.setHotScore(playNum*0.1+subscribeNum*0.2+commentNum*0.3+buyNum*0.4);
        //5
        Result<List<AlbumAttributeValue>> albumAttributeList = albumInfoFeignClient.getAlbumAttributeById(albumInfo.getId());
        Assert.notNull(albumAttributeList,"数据导入时，获取标签值集合失败");
        List<AlbumAttributeValue> attributeListData = albumAttributeList.getData();
        if (!CollectionUtils.isEmpty(attributeListData)){
            List<AttributeValueIndex> attributeValueIndices = attributeListData.stream().map(albumAttributeValue -> {
                AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                BeanUtils.copyProperties(albumAttributeValue, attributeValueIndex);
                return attributeValueIndex;
            }).collect(Collectors.toList());
            albumInfoIndex.setAttributeValueIndexList(attributeValueIndices);
        }
        elasticsearchTemplate.save(albumInfoIndex);

    }

    @Override
    public void lower(String albumId) {
        elasticsearchTemplate.delete(albumId,AlbumInfoIndex.class);
    }

    @Override
    public List<HomePageVo> channel(Long category3Id) {
        Result<List<BaseCategory3>> topBaseCategory3ListResult = categoryFeignClient.findTopBaseCategory3(category3Id);
        Assert.notNull(topBaseCategory3ListResult,"查询置顶频道失败");
        List<BaseCategory3> baseCategory3List = topBaseCategory3ListResult.getData();
        Assert.notEmpty(baseCategory3List,"查询置顶频道失败");
        Map<Long, BaseCategory3> map = baseCategory3List.stream().collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));
        List<FieldValue> category3IdList = baseCategory3List.stream().map(baseCategory3 -> FieldValue.of(baseCategory3.getId())).collect(Collectors.toList());

        SearchRequest request = SearchRequest.of(s->s.size(0)
                .index("albuminfo")
                .query(q->q.terms(t->t.field("category3Id").terms(ts->ts.value(category3IdList))))
                .aggregations("categoryAgg",a->a.terms(t->t.field("category3Id"))
                        .aggregations("hotScoreTop",ag->ag.topHits(h->h.size(6)
                                .sort(st->st.field(f->f.field("hotScore").order(SortOrder.Desc)))))));

        try {
            SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(request, AlbumInfoIndex.class);
            Map<String, Aggregate> aggregations = response.aggregations();
            if (CollectionUtils.isEmpty(aggregations)){
                return null;
            }
            Aggregate categoryAgg = aggregations.get("categoryAgg");
            if (categoryAgg==null){
                return null;
            }
            List<LongTermsBucket> bucketList = categoryAgg.lterms().buckets().array();
            if (!CollectionUtils.isEmpty(bucketList)){
                List<HomePageVo> top = bucketList.stream().map(longTermsBucket -> {
                    HomePageVo homePageVo = new HomePageVo();

                    long category3IdBucket = longTermsBucket.key();
                    homePageVo.setBaseCategory3(map.get(category3IdBucket));
                    Map<String, Aggregate> subAggregate = longTermsBucket.aggregations();
                    if (CollectionUtils.isEmpty(subAggregate)) {
                        return homePageVo;
                    }
                    Aggregate hotScoreTop = subAggregate.get("hotScoreTop");
                    TopHitsAggregate topHitsAggregate = hotScoreTop.topHits();
                    if (topHitsAggregate == null) {
                        return homePageVo;
                    }
                    HitsMetadata<JsonData> hits = topHitsAggregate.hits();
                    List<Hit<JsonData>> hitsList = hits.hits();
                    if (CollectionUtils.isEmpty(hitsList)) {
                        return homePageVo;
                    }
                    homePageVo.setList(hitsList.stream().map(hit -> hit.source().to(AlbumInfoIndex.class)).collect(Collectors.toList()));
                    return homePageVo;
                }).collect(Collectors.toList());
                return top;
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {

        try {
            SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(buildDsl(albumIndexQuery), AlbumInfoIndex.class);
            AlbumSearchResponseVo albumSearchResponseVo = parseResult(response);
            albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
            albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
            long totalPage = albumSearchResponseVo.getTotal() / albumSearchResponseVo.getPageSize();
            albumSearchResponseVo.setTotalPages(albumSearchResponseVo.getTotal() % albumSearchResponseVo.getPageSize()==0 ? totalPage:totalPage+1);


            return albumSearchResponseVo;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long category1Id, String field) {


        try {
            SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(s -> s.index("albuminfo").query(q -> q.term(t -> t.field("category1Id").value(2)))
                            .size(20)
                            .sort(st -> st.field(f -> f.field(field).order(SortOrder.Desc)))
                    , AlbumInfoIndex.class);
            HitsMetadata<AlbumInfoIndex> hits = response.hits();
            List<Hit<AlbumInfoIndex>> hitList = hits.hits();
            List<AlbumInfoIndexVo> albumInfoIndexVoList = hitList.stream().map(hit -> {
                AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
                AlbumInfoIndex source = hit.source();
                BeanUtils.copyProperties(source, albumInfoIndexVo);
                return albumInfoIndexVo;
            }).collect(Collectors.toList());

            return albumInfoIndexVoList;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<String> completeSuggest(String prefix) {
        List<String> suggestList=new ArrayList<>();
        if (StringUtils.isBlank(prefix)){
            return suggestList;
        }
        try {
            SearchRequest request = SearchRequest.of(s -> s.index("suggestinfo").suggest(sg -> sg
                    .suggesters("keywordSuggest", ks -> ks.prefix(prefix).completion(c -> c.field("keyword").size(10).skipDuplicates(true)))
                    .suggesters("keywordPinyinSuggest", ks -> ks.prefix(prefix).completion(c -> c.field("keywordPinyin").size(10).skipDuplicates(true)))
                    .suggesters("keywordSequenceSuggest", ks -> ks.prefix(prefix).completion(c -> c.field("keywordSequence").size(10).skipDuplicates(true)))));
            System.out.println(request);
            SearchResponse<SuggestIndex> response = elasticsearchClient.search(request, SuggestIndex.class);

            Map<String, List<Suggestion<SuggestIndex>>> suggestMap = response.suggest();
            Suggestion<SuggestIndex> keywordSuggest = suggestMap.get("keywordSuggest").get(0);
            Suggestion<SuggestIndex> keywordPinyinSuggest = suggestMap.get("keywordPinyinSuggest").get(0);
            Suggestion<SuggestIndex> keywordSequenceSuggest = suggestMap.get("keywordSequenceSuggest").get(0);

            HashSet<String> suggestSet = new HashSet<>();
            List<String> keywordList = getTitlesFromSuggestion(keywordSuggest);
            List<String> keywordPinyinList = getTitlesFromSuggestion(keywordPinyinSuggest);
            List<String> keywordSequenceList = getTitlesFromSuggestion(keywordSequenceSuggest);
            suggestSet.addAll(keywordList);
            suggestSet.addAll(keywordPinyinList);
            suggestSet.addAll(keywordSequenceList);
            suggestList.addAll(suggestSet);
            if (suggestList.size() >= 10) {
                return suggestList;
            }
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(s -> s.index("albuminfo").query(q -> q.fuzzy(f -> f.field("albumTitle").value(prefix).fuzziness("2"))), AlbumInfoIndex.class);
            List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
            hitList.forEach(hit->
            {
                if (suggestList.size() >= 10) {
                    return;
                }
                suggestList.add(hit.source().getAlbumTitle());
            });

            return suggestList;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    private List<String> getTitlesFromSuggestion(Suggestion<SuggestIndex> keywordSuggest) {
        List<CompletionSuggestOption<SuggestIndex>> optionList = keywordSuggest.completion().options();
        List<String> keywordList = optionList.stream().map(option -> option.source().getTitle()).collect(Collectors.toList());
        return keywordList;
    }

    private SearchRequest buildDsl(AlbumIndexQuery albumIndexQuery){
        SearchRequest.Builder requestBuilder = new SearchRequest.Builder();

        requestBuilder.index("albuminfo");
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();

        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.isNotBlank(keyword)){
            boolQueryBuilder.must(m->m.multiMatch(mm->mm
                    .query(keyword)
                    .fields("albumTitle","albumIntro","announcerName")
                    .operator(Operator.And)));
        }
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id!=null){
            boolQueryBuilder.filter(f->f.term(t->t.field("category1Id").value(category1Id)));
        }
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id!=null){
            boolQueryBuilder.filter(f->f.term(t->t.field("category2Id").value(category2Id)));
        }
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id!=null){
            boolQueryBuilder.filter(f->f.term(t->t.field("category3Id").value(category3Id)));
        }

        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)){
            attributeList.forEach(attribute->{
                String[] attrArr = StringUtils.split(attribute, ":");
            if (attrArr!=null && attrArr.length==2 && StringUtils.isNumeric(attrArr[0]) && StringUtils.isNumeric(attrArr[1])){

                }
            boolQueryBuilder.filter(f->f.nested(n->n
                    .path("attributeValueIndexList")
                    .query(q->q.bool(b->b
                            .must(m->m.term(t->t.field("attributeValueIndexList.attributeId").value(attrArr[0])))
                            .must(m->m.term(t->t.field("attributeValueIndexList.valueId").value(attrArr[1])))))));});
        }

        requestBuilder.query(boolQueryBuilder.build()._toQuery());
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)){
            String[] orders = StringUtils.split(order, ":");
            List<String> stringList = Arrays.asList("asc", "desc");
            if (orders!=null && orders.length==2 && stringList.contains(orders[1])){
                String fileName=null;
                switch (orders[0]){
                    case "1":fileName="hotScore";break;
                    case "2":fileName="playStatNum";break;
                    case "3":fileName="createTime";break;
                }
                String finalFileName=fileName;
                requestBuilder.sort(s->s.field(f->f.field(finalFileName).order(StringUtils.equals(orders[1],"desc")?SortOrder.Desc:SortOrder.Asc)));
            }
        }

        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        requestBuilder.from((pageNo-1)*pageSize);
        requestBuilder.size(pageSize);

        requestBuilder.highlight(h->h.fields("albumTitle",f->f.preTags("<font style='color:red'>").postTags("</font>"))
                                     .fields("albumIntro",f->f.preTags("<font style='color:red'>").postTags("</font>")));

        requestBuilder.source(s->s.filter(f->f.excludes("createTime")));
        SearchRequest request = requestBuilder.build();

        System.out.println(request);
        return request;
    }
    private AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> response){
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        HitsMetadata<AlbumInfoIndex> hits = response.hits();
        albumSearchResponseVo.setTotal(hits.total().value());
        List<Hit<AlbumInfoIndex>> hitList = hits.hits();
        List<AlbumInfoIndexVo> albumInfoIndexVoList = hitList.stream().map(hit -> {
            AlbumInfoIndex source = hit.source();
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            BeanUtils.copyProperties(source, albumInfoIndexVo);
            Map<String, List<String>> highlight = hit.highlight();
            if (CollectionUtils.isEmpty(highlight)){
                return albumInfoIndexVo;
            }
            List<String> albumTitleList = highlight.get("albumTitle");
            if (!CollectionUtils.isEmpty(albumTitleList)){
                albumInfoIndexVo.setAlbumTitle(albumTitleList.get(0));
            }
            List<String> albumIntroList = highlight.get("albumIntro");
            if (!CollectionUtils.isEmpty(albumIntroList)){
                albumInfoIndexVo.setAlbumIntro(albumIntroList.get(0));
            }
            return albumInfoIndexVo;
        }).collect(Collectors.toList());
        albumSearchResponseVo.setList(albumInfoIndexVoList);
        return albumSearchResponseVo;
    }
}
